<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="robots" content="noodp" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
        <title>《Flutter渲染原理与性能优化》 - Lucklyの博客 - 在阅读中遇见自己</title><meta name="Description" content="早起的年轻人做的网站"><meta property="og:title" content="《Flutter渲染原理与性能优化》" />
<meta property="og:description" content="
本文主要介绍
" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" />
<meta property="og:image" content="https://luckly.work/logo.png"/>
<meta property="article:published_time" content="2021-07-31T21:39:34+08:00" />
<meta property="article:modified_time" content="2021-07-31T21:39:34+08:00" />
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://luckly.work/logo.png"/>

<meta name="twitter:title" content="《Flutter渲染原理与性能优化》"/>
<meta name="twitter:description" content="
本文主要介绍
"/>
<meta name="application-name" content="LoveIt">
<meta name="apple-mobile-web-app-title" content="LoveIt"><meta name="theme-color" content="#ffffff"><meta name="msapplication-TileColor" content="#da532c"><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"><link rel="manifest" href="/site.webmanifest"><link rel="canonical" href="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" /><link rel="prev" href="https://luckly.work/flutter%E6%B5%8B%E8%AF%95%E8%84%9A%E6%9C%AC%E5%BC%80%E5%8F%91/" /><link rel="next" href="https://luckly.work/flutter%E7%95%8C%E9%9D%A2%E8%B7%B3%E8%BD%AC%E6%95%B0%E6%8D%AE%E4%BA%A4%E4%BA%92%E7%95%8C%E9%9D%A2%E6%B7%B7%E5%90%88%E5%BC%80%E5%8F%91/" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.min.css"><link rel="stylesheet" href="/css/style.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.7.2/animate.min.css"><script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "BlogPosting",
        "headline": "《Flutter渲染原理与性能优化》",
        "inLanguage": "zh-CN",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "https:\/\/luckly.work\/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96\/"
        },"image": [{
                            "@type": "ImageObject",
                            "url": "https:\/\/luckly.work\/images\/Apple-Devices-Preview.png",
                            "width":  3200 ,
                            "height":  2048 
                        }],"genre": "posts","keywords": "flutter","wordcount":  13893 ,
        "url": "https:\/\/luckly.work\/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96\/","datePublished": "2021-07-31T21:39:34+08:00","dateModified": "2021-07-31T21:39:34+08:00","license": "This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.","publisher": {
            "@type": "Organization",
            "name": "xxxx","logo": {
                    "@type": "ImageObject",
                    "url": "https:\/\/luckly.work\/images\/avatar.png",
                    "width":  528 ,
                    "height":  560 
                }},"author": {
                "@type": "Person",
                "name": "早起的年轻人"
            },"description": ""
    }
    </script></head>
    <body header-desktop="fixed" header-mobile="auto"><script type="text/javascript">(window.localStorage && localStorage.getItem('theme') ? localStorage.getItem('theme') === 'dark' : ('auto' === 'auto' ? window.matchMedia('(prefers-color-scheme: dark)').matches : 'auto' === 'dark')) && document.body.setAttribute('theme', 'dark');</script>

        <div id="mask"></div><div class="wrapper"><div class="logo-wrapper">
  <a href="/%20/" class="logo">Lucklyの博客 - 在阅读中遇见自己</a>
</div>

<nav class="site-navbar">
  <ul id="menu" class="menu">
    <li class="menu-item">
      <a class="menu-item-link" href="/posts/">所有文章</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/flutter/">Flutter</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/read/">读书笔记</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/go/">Go</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/tags/">标签</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/categories/">分类</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/about/">关于</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="https://github.com/ITmxs">Github</a>
    </li><li class="menu-item">
      <a class="menu-item-link" href="/friend/">友链</a>
    </li>
  </ul>
</nav><main class="main">
                <div class="container"><div class="toc" id="toc-auto">
            <h2 class="toc-title">目录</h2>
            <div class="toc-content" id="toc-content-auto"></div>
        </div><article class="page single"><h1 class="single-title animated flipInX">《Flutter渲染原理与性能优化》</h1><div class="post-meta">
            <div class="post-meta-line"><span class="post-author"><a href="/" title="Author" rel=" author" class="author"><i class="fas fa-user-circle fa-fw"></i>早起的年轻人</a></span>&nbsp;<span class="post-category">收录于 <a href="/categories/flutter/"><i class="far fa-folder fa-fw"></i>Flutter</a></span></div>
            <div class="post-meta-line"><i class="far fa-calendar-alt fa-fw"></i>&nbsp;<time datetime="2021-07-31">2021-07-31</time>&nbsp;<i class="fas fa-pencil-alt fa-fw"></i>&nbsp;约 13893 字&nbsp;
                <i class="far fa-clock fa-fw"></i>&nbsp;预计阅读 28 分钟&nbsp;</div>
        </div><div class="featured-image"><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"
        title="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png" /></div><div class="details toc" id="toc-static"  kept="">
                <div class="details-summary toc-title">
                    <span>目录</span>
                    <span><i class="details-icon fas fa-angle-right"></i></span>
                </div>
                <div class="details-content toc-content" id="toc-content-static"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#flutter示例说明">Flutter示例说明</a></li>
        <li><a href="#widge类型说明">Widge类型说明</a></li>
        <li><a href="#界面构建过程">界面构建过程</a>
          <ul>
            <li><a href="#widgetelement和renderobject三者关系">Widget、Element和RenderObject三者关系</a></li>
            <li><a href="#flutter与android界面开发对比">Flutter与Android界面开发对比</a></li>
            <li><a href="#界面创建过程">界面创建过程</a></li>
            <li><a href="#界面刷新过程">界面刷新过程</a></li>
          </ul>
        </li>
        <li><a href="#界面渲染过程">界面渲染过程</a>
          <ul>
            <li><a href="#约束条件">约束条件</a></li>
            <li><a href="#继续渲染过程分析">继续渲染过程分析</a></li>
          </ul>
        </li>
        <li><a href="#性能优化">性能优化</a></li>
        <li><a href="#flutter生命周期">Flutter生命周期</a>
          <ul>
            <li><a href="#state生命周期">State生命周期</a></li>
            <li><a href="#app生命周期">App生命周期</a></li>
            <li><a href="#widgetelement-与-renderobject生命周期">Widget、Element 与 RenderObject生命周期</a></li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</nav></div>
            </div><div class="content" id="content"><blockquote>
<p>本文主要介绍</p>
</blockquote>
<p>之前的内容我们讲的是Flutter的基本布局控件使用方式，包括单元素布局与多元素布局。为什么会有这两种区分呢？还有一些控件根本没有布局方式，比如RichText，那它是怎么展示的？</p>
<p>这一节我们就来深入看下Flutter的Widget是怎么构建、布局、渲染的，它们之间又靠什么联系在一起。</p>
<p>首先我们从官方示例开始看起。</p>
<h2 id="flutter示例说明">Flutter示例说明</h2>
<p>使用Android Studio创建Flutter工程，会创建一个默认的界面，我们可以从这个界面分析出一点内容。</p>
<p><code>main.dart</code> 内容如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">import &#39;package:flutter/material.dart&#39;;

void main() =&gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;Flutter Demo&#39;,
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with &#34;flutter run&#34;. You&#39;ll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // &#34;hot reload&#34; (press &#34;r&#34; in the console where you ran &#34;flutter run&#34;,
        // or simply save your changes to &#34;hot reload&#34; in a Flutter IDE).
        // Notice that the counter didn&#39;t reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: &#39;Flutter Demo Home Page&#39;),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked &#34;final&#34;.

  final String title;

  @override
  _MyHomePageState createState() =&gt; _MyHomePageState();
}

class _MyHomePageState extends State&lt;MyHomePage&gt; {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke &#34;debug painting&#34; (press &#34;p&#34; in the console, choose the
          // &#34;Toggle Debug Paint&#34; action from the Flutter Inspector in Android
          // Studio, or the &#34;Toggle Debug Paint&#34; command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            Text(
              &#39;You have pushed the button this many times:&#39;,
            ),
            Text(
              &#39;$_counter&#39;,
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: &#39;Increment&#39;,
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
</code></pre></td></tr></table>
</div>
</div><p>之前写了很多常用widget的使用方法，但是还没有系统地对展示过程进行说明，这里对Demo中的每一行作用，进行详细说明：</p>
<ul>
<li><code>import 'package:flutter/material.dart';</code>这是dart语言的用法，这里导入的是MaterialDesign风格的包，里面有各种MaterialDesign风格控件以及常用控件。我们常用的MaterialApp、Scaffold、RaisedButton、Card等，都属于MaterialDesign风格的控件。</li>
<li><code>void main() =&gt; runApp(MyApp());</code> 这个是主方法入口，<code>main</code>方法为程序调用方法，<code>runApp</code>方法将一个Widget加到根节点中，添加方式稍后再原理解析中详细说明。<code>MyApp()</code>创建了一个Widget对象，下面的代码是MyApp类的声明。</li>
<li><code>MyApp</code>类声明中，说明了这是一个StatelessWidget的子类。也就是说，这个类应该是无状态的，里面的字段应该是不可变更的，为final类型。如果想要更新这个Widget的展示效果，只能重新创建一个新的对象来实现而不能复用原来对象。</li>
<li>MyApp的<code>build</code>方法返回了需要展示的Widget内容，这里返回的Widget和MyApp本身没有关系，只是他们的Element有关系，这一点要弄清楚，我之前也是在这一项卡了很久才理解。如果真的要想象这两个有关系，可以认为是父子关系。在这里返回的Widget是MaterialApp，这是一个典型的MaterialDesign应用，里面指定了theme，而在home中设置的是真正的界面widget.另外MaterialApp中也支持路由设置。</li>
<li><code>MyHomePage</code>类声明中，说明了这是一个StatefulWidget的子类，它与StatelessWidget的区别在于它无需重建类即可变更界面内容，变更方式通过State来实现。</li>
<li><code>_MyHomePageState</code>中存储了<code>MyHomePage</code>的状态信息，在State中进行变更，界面会随之变动。同样通过build方法提供界面展示效果，里面返回的是一个<code>Scaffold</code>类型Widget，这是最常用的MaterialDesign风格设计框架，提供了各种展示效果。在Demo中分别提供了AppBar、界面主体以及一个浮动按钮。在<code>_MyHomePageState</code>中还设置了一个方法，点击按钮时，更新界面展示内容。</li>
</ul>
<p>下面是一个这个界面的效果：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214258337.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214258337.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214258337.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214258337.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214258337.png"
        title="image-20210731214258337" /></p>
<h2 id="widge类型说明">Widge类型说明</h2>
<p>在Demo中使用到了几种Widget？一一列出来：MyApp、MaterialApp、MyHomePage、Scaffold、AppBar、Text、Center、Column、FloatingActionButton、Icon.</p>
<p>这么多组件中，我们自己定义的只有两个：MyApp、MyHomePage，而这两个控件的父类，分别是：StatelessWidget、StatefulWidget，这两种也是Flutter推荐使用的两个父控件类型。</p>
<p>按照官方的设计原则，StatelessWidget、StatefulWidget这两种组合Widget基本上可以满足需要的所有情况，不过这不意味着我们不能自定义其它Widget.</p>
<p>下面列出几种经常遇到的基础Widget：</p>
<ul>
<li>StatelessWidget 组合型Widget，widget实例创建后，不可更改。比如：Container、RaisedButton等，如果有变动，需要重新创建实例。</li>
<li>StatefulWidget 组合型Widget，widget实例携带一个state实例，state实例内容可变更。这种Widget一般用于界面绘制的桥梁。但是如果滥用也会产生性能问题，本文后面会根据源码给出性能优化建议。</li>
<li>RenderObjectWidget 渲染型Widget，这种一般是基础widget，可以将该widget内容渲染出来。</li>
<li>SingleChildRenderObjectWidget 渲染型Widget，这种是RenderObjectWidget的一个子类，特点是该Widget只有一个子控件，比如之前介绍过的Padding、Align等。</li>
<li>MultiChildRenderObjectWidget 渲染型Widget，这种同样是RenderObjectWidget的一个子类，特点是该widget可以有超过一个的子控件，比如之前介绍过的Flex等。</li>
<li>LeafRenderObjectWidget 渲染型Widget，这种同样是RenderObjectWidget的一个子类，特点是该widget不会有子控件，只是由其本身进行渲染，比如RichText、RawImage等。</li>
<li>ProxyWidget 代理型Widget，这种widget用来进行数据在widget之间传递，比如常用的InheritedWidget，一般的状态管理框架也是基于这个原理实现的。</li>
</ul>
<p>我们的Demo，乃至于各种复杂的界面，主要也是这几种Widget组成的。</p>
<h2 id="界面构建过程">界面构建过程</h2>
<p>上面说了几种基础Widget类型，如果将Demo中涉及到的Widget整理成一个树（实际上widget不算是一个真正的树，至少不是一个静态树，真正的树是Element和RenderObject），可以看到如下结构：
<img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/Demo%E7%9A%84Widget%E6%A0%91.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/Demo%E7%9A%84Widget%E6%A0%91.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/Demo%E7%9A%84Widget%E6%A0%91.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/Demo%E7%9A%84Widget%E6%A0%91.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/Demo%E7%9A%84Widget%E6%A0%91.png"
        title="Demo的Widget树" /></p>
<p>在树形结构中可以看到Widget的互相依赖过程，但是这个Widget树是怎样变成我们可见的界面的？中间经过了哪些转换过程？我们先说一下两个开发时没有涉及到的东西：<code>Element</code>和<code>RenderObject</code></p>
<ul>
<li>Element 这个是真正的节点，用来关联Widget与渲染对象。每一个Widget都对应着一个Element，但是Widget实例会经常变动，但是渲染树不能经常改变，因此Element会尽可能改变而不是重新创建。</li>
<li>RenderObject 是一个渲染节点数，这里面的每一个节点都会在界面上绘制出来。RenderObject与Element或者Widget不是一一对应的，只有RenderObjectWidget以及它的子类才会存在RenderObject</li>
</ul>
<h3 id="widgetelement和renderobject三者关系">Widget、Element和RenderObject三者关系</h3>
<p>上面简单介绍了Element、RenderObject，但是感觉还是不清楚，下面我们通过Flutter源码进行说明：</p>
<p>查看<code>Widget</code>类的代码，里面有这样一项：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">Element createElement();
</code></pre></td></tr></table>
</div>
</div><p>这个就是Widget对应的Element，在不同的Widget中，会创建不同的Element，比如StatelessElement、StatefulElement、RenderObjectElement、SingleChildRenderObjectElement、MultiChildRenderObjectElement、LeafRenderObjectElement、ProxyElement等。因为是一一对应，同样在Element中也会持有Widget的对象：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">@override
  Widget get widget =&gt; _widget;
  Widget _widget;
</code></pre></td></tr></table>
</div>
</div><p>而RenderObject则针对的是可渲染Widget，也就是RenderObjectWidget，部分代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">RenderObject createRenderObject(BuildContext context);
</code></pre></td></tr></table>
</div>
</div><p>也就是说会在RenderObjectWidget的子类中创建RenderObject，但是实际上这样做只是为了方便开发人员使用，真正情况还是在Element中持有RenderObject对象，如下面代码所示：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">RenderObject get renderObject =&gt; _renderObject;
  RenderObject _renderObject;
  
void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    ...
  }
</code></pre></td></tr></table>
</div>
</div><p>所以三者的情况就是Widget用于开发人员设计控件，RenderObject用于界面渲染，Element连接两者展示。</p>
<h3 id="flutter与android界面开发对比">Flutter与Android界面开发对比</h3>
<p>Android界面开发是命令式的，如果需要变更某一个View，需要获取该View的句柄，然后对view进行参数变更。这样的实现方式有很强的针对性，同时绘制过程中也会自动判断，可以精准的对某一个View进行绘制。但是这种方式有一些缺点，如果变动的view比较多时，就需要为每一项单独设置，而且需要开发人员自己控制的话，较为复杂的界面逻辑需要很强的处理能力。</p>
<p>Flutter界面开发是声明式的，每次只要定义好数据项，同时声明这些数据项与Widget的绑定关系。真正使用时，只需要变更数据内容，然后重建Widget实例就可以了。这种方式的优势就是简单粗暴，并更数据集后，绑定的相关界面项会自动调整。但是所有相关Widget都换了一遍，如果渲染内容的实例（也就是RenderObject）也都重新变更一遍，那对于界面效果来说，是一个非常严重的打击。</p>
<p>但是实际上，Flutter的渲染效率很高，Widget虽然重建，但是Element以及RenderObject不一定会进行重建，具体的渲染过程，我们可以跟踪源码来看下。</p>
<h3 id="界面创建过程">界面创建过程</h3>
<p>main方法是主入口，里面只调用了runApp方法。可以看下这个方法的实现：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}
</code></pre></td></tr></table>
</div>
</div><p>注意里面的<code>attachRootWidget</code>方法，这个方法是将我们提供的Widget添加到RootWidget中，具体代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter&lt;RenderBox&gt;(
      container: renderView,
      debugShortDescription: &#39;[root]&#39;,
      child: rootWidget
    ).attachToRenderTree(buildOwner, renderViewElement);
  }
</code></pre></td></tr></table>
</div>
</div><p>这里又调用了<code>attachToRenderTree</code>，这个方法将当前渲染内容加入到渲染树中，看相关代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">RenderObjectToWidgetElement&lt;T&gt; attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement&lt;T&gt; element]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element.assignOwner(owner);
      });
      owner.buildScope(element, () {
        element.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }
</code></pre></td></tr></table>
</div>
</div><p>初始情况下，RootElement为空，因此创建新的Element。然后通过<code>BuildOwner.buildScope</code>将Element树以及RenderObject树添加子节点。实际上<code>buildScope</code>这个方法不仅在创建界面时用到，刷新界面时同样用到了，后面刷新界面时详细说明一下。</p>
<p>在创建界面时buildScope方法可以简化为：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void buildScope(Element context, [VoidCallback callback]) {
    ···
        try {
          callback();
        } finally {
          ···
        }
      }
      ···
  }
</code></pre></td></tr></table>
</div>
</div><p>由此可以看出，实际上只是执行了传入的callback方法。继续根据传入的方法，只是执行了如下语句：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">element.mount(null, null);
</code></pre></td></tr></table>
</div>
</div><p>这个是根节点进行mount操作，因为没有父节点，所以parent传为空，查看相应代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void mount(Element parent, dynamic newSlot) {
    assert(parent == null);
    super.mount(parent, newSlot);
    _rebuild();
  }
</code></pre></td></tr></table>
</div>
</div><p><code>_rebuild()</code>代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
      assert(_child != null);
    } catch (exception, stack) {
      final FlutterErrorDetails details = FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: &#39;widgets library&#39;,
        context: &#39;attaching to the render tree&#39;
      );
      FlutterError.reportError(details);
      final Widget error = ErrorWidget.builder(details);
      _child = updateChild(null, error, _rootChildSlot);
    }
  }
</code></pre></td></tr></table>
</div>
</div><p>注意，<code>updateChild</code>同样也是界面创建于刷新时的重要处理过程，后面会详细说明，这里只需要认为这里会进行子控件的添加，而且是递归添加处理，分别调用子控件的<code>mount</code>操作。其中<code>widget.child</code>就是我们传入的Widget实例，在Demo中就是<code>MyApp()</code>。</p>
<p>查看<code>Element</code>类的mount方法，这里将RenderObject加入到相应的树中。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    assert(() { _debugUpdateRenderObjectOwner(); return true; }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }
  
void attachRenderObject(dynamic newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
    final ParentDataElement&lt;RenderObjectWidget&gt; parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
  }
</code></pre></td></tr></table>
</div>
</div><p>之前我们说过，Element与RenderObject不是一一对应的，所以需要寻找到可用的父RenderObject，再添加新的节点。</p>
<h3 id="界面刷新过程">界面刷新过程</h3>
<p>上面的创建过程很好理解，每个Widget都有一个Element，同时也与RenderObject保持关系。但是这样做很麻烦，为什么不直接创建一个渲染节点呢？就像Android那样做？还要维护三者间的关系。</p>
<p>我们之前也写过，Widget是可以随意创建的，但是Element却要尽可能地保持复用，所以刷新时这三者关系还要再好好设计。</p>
<p>界面刷新需要一个切入点(比如Android通过<code>invalidate</code>通知)，在Flutter中，就是通过State的<code>setState</code>方法来进行刷新。</p>
<p>查看setState源码，去掉一些无用代码</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void setState(VoidCallback fn) {
	···
    _element.markNeedsBuild();
  }
</code></pre></td></tr></table>
</div>
</div><p>很简单，就是调用Element的<code>markNeedsBuild</code>方法，继续查看：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void markNeedsBuild() {
    ···
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
</code></pre></td></tr></table>
</div>
</div><p>这里面将Element标记为dirty，然后调用<code>scheduleBuildFor</code>方法，继续查看：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void scheduleBuildFor(Element element) {
    ···
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements &amp;&amp; onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
    ···
  }
</code></pre></td></tr></table>
</div>
</div><p>这里将该element加入到_dirtyElements中，标记这个节点刷新时需要进行处理。然后执行了<code>onBuildScheduled</code>方法。这个方法进行了什么操作，继续查找源码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">buildOwner.onBuildScheduled = _handleBuildScheduled;

void _handleBuildScheduled() {
	···
	ensureVisualUpdate();
}

void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

void scheduleFrame() {
    ···
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }
  
void scheduleFrame() native &#39;Window_scheduleFrame&#39;;
</code></pre></td></tr></table>
</div>
</div><p>终于找到了，调用了一个native方法 <code>Window_scheduleFrame</code>，这个方法在Flutter Engine中实现。查看注释内容，会回调<code>onBeginFrame</code>和<code>onDrawFrame</code>这两个方法。继续查找源码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">window.onBeginFrame = _handleBeginFrame;
window.onDrawFrame = _handleDrawFrame;
···
</code></pre></td></tr></table>
</div>
</div><p>由于相关代码较多，这里简化一下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void drawFrame() {
    ···
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      ···
    }
    ···
  }
</code></pre></td></tr></table>
</div>
</div><p>又调用到了<code>buildOwner.buildScope</code>方法！</p>
<p>之前创建界面时采用了这个方法，现在刷新时也用到了，详细说明一下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void buildScope(Element context, [VoidCallback callback]) {
    if (callback == null &amp;&amp; _dirtyElements.isEmpty)
      return;
    ···
    Timeline.startSync(&#39;Build&#39;, arguments: timelineWhitelistArguments);
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
        ···
        _dirtyElementsNeedsResorting = false;
        try {
          callback();
        } finally {
          ···
      	 }
      }
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index &lt; dirtyCount) {
        ···
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ···
        }
        index += 1;
        if (dirtyCount &lt; _dirtyElements.length || _dirtyElementsNeedsResorting) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index &gt; 0 &amp;&amp; _dirtyElements[index - 1].dirty) {
            // It is possible for previously dirty but inactive widgets to move right in the list.
            // We therefore have to move the index left in the list to account for this.
            // We don&#39;t know how many could have moved. However, we do know that the only possible
            // change to the list is that nodes that were previously to the left of the index have
            // now moved to be to the right of the right-most cleaned node, and we do know that
            // all the clean nodes were to the left of the index. So we move the index left
            // until just after the right-most clean node.
            index -= 1;
          }
        }
      }
      ···
        return true;
      }());
    } finally {
      for (Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      Timeline.finishSync();
      ···
      }
  }
</code></pre></td></tr></table>
</div>
</div><p>代码中可以看到，首先将_dirtyElements进行排序，这是因为节点可能有很多个，如果其中两个节点存在级联关系，父级的Widget build操作必然会调用到子级的Widget build，如果子级又自己build一次，相当于出现了重复操作。因此通过深度排序就会避免这个问题。</p>
<p>排序结束后，对每一个Element进行遍历，执行rebuild操作。需要注意的是，如果在遍历过程中增加了新的节点，那么就需要重新排序。rebuild操作后面详细说明。</p>
<p>所有Element都rebuild后，清空_dirtyElements集合，节点状态恢复正常。</p>
<p>rebuild示意代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void rebuild() {
    ···
    if (!_active || !_dirty)
      return;
    ···
    performRebuild();
    ···
  }
</code></pre></td></tr></table>
</div>
</div><p>继续跟踪代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void performRebuild() {
    ···
    Widget built;
    try {
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      built = ErrorWidget.builder(_debugReportException(&#39;building $this&#39;, e, stack));
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      ···
    }
    try {
      _child = updateChild(_child, built, slot);
      ···
    } catch (e, stack) {
      built = ErrorWidget.builder(_debugReportException(&#39;building $this&#39;, e, stack));
      _child = updateChild(null, built, slot);
    }
    ···
  }
</code></pre></td></tr></table>
</div>
</div><p>这里的<code>build</code>方法最终调用的是Widget中对应的build方法。<code>updateChild</code>方法同样也是创建界面时调用的方法，继续跟踪源码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ···
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    if (child != null) {
      if (child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        ···
        return child;
      }
      deactivateChild(child);
      ···
    }
    return inflateWidget(newWidget, newSlot);
  }
</code></pre></td></tr></table>
</div>
</div><p>这个方法就是Widget实例变更，但是Element实例不变的核心了，这里分成了四种情况分别处理：</p>
<ul>
<li>如果不存在新的Widget，那么说明这一个节点应该取消掉了，执行<code>deactivateChild</code>方法。</li>
<li>如果子节点的widget和新的widget一致（这里的一致指的是同一个对象，这个也是允许的），直接返回这个子节点。</li>
<li>如果两个widget不是同一个对象，判断类型是否相同，通过<code>canUpdate</code>方法判断，依据是Widget类型一致，同时Key一致。这种情况下，只需要更新子节点就好了。因此这一步就是widget变更，但是element不变更的原因。</li>
<li>其它情况下则认为子节点是新增的，调用<code>inflateWidget</code>进行子节点创建，里面与创建界面相同，执行了mount操作。</li>
</ul>
<p>上面的代码都是ComponentElement的类中处理方式，也就是常用的StatelessWidget与StatefulWidget使用的Element。这个过程比较复杂，按照我个人的见解来说，如果想要不进行变更，父级的Widget是不能改变的，否则无法找到锚点，所以界面刷新都是从StatefulWidget开始的，而不能从StatelessWidget开始。刷新开始后，在rebuild中进行递归处理，以StatefulWidget实例为锚点，一级一级地维护Widget与Element的关系。</p>
<p>如果不是ComponentElement，针对RenderObjectElement，则会调用下面的处理：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void performRebuild() {
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
</code></pre></td></tr></table>
</div>
</div><p>每一种RenderObjectElement都会有自己的<code>updateRenderObject</code>处理方式，类似于Android的View操作，针对每一个View来设置属性，这里不再详细说明。</p>
<p>到这里，<code>buildOwner.buildScope(renderViewElement);</code>方法就已经结束了，还记得不，再贴一下drawFrame代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void drawFrame() {
    ···
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      ···
    }
    ···
  }
</code></pre></td></tr></table>
</div>
</div><p>剩下执行<code>super.drawFrame();</code>，通过pipelineOwner将RenderObject绘制到界面上，不再详细说说明：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }
</code></pre></td></tr></table>
</div>
</div><p>然后再执行<code>buildOwner.finalizeTree();</code>，这里面将一些设置为deactive的Element进行回收，这部分涉及到了生命周期，下面会详细说明。</p>
<h2 id="界面渲染过程">界面渲染过程</h2>
<p>继续上面的内容，前面说了界面构建的过程，创建好了RenderObject树，那么RenderObject tree怎么进行渲染呢？</p>
<p>继续看<code>drawFrame</code>方法：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }
</code></pre></td></tr></table>
</div>
</div><p><code>pipelineOwner.flushLayout()</code>对需要relayout的RenderObject对象重新测量。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void flushLayout() {
    profile(() {
      Timeline.startSync(&#39;Layout&#39;, arguments: timelineWhitelistArguments);
    });
    ···
    try {
      // TODO(ianh): assert that we&#39;re not allowing previously dirty nodes to redirty themselves
      while (_nodesNeedingLayout.isNotEmpty) {
        final List&lt;RenderObject&gt; dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = &lt;RenderObject&gt;[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) =&gt; a.depth - b.depth)) {
          if (node._needsLayout &amp;&amp; node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {
      ···
      profile(() {
        Timeline.finishSync();
      });
    }
  }
</code></pre></td></tr></table>
</div>
</div><p>先将_nodesNeedingLayout集合根据节点深度进行排序，然后重新进行layout。_nodesNeedingLayout集合的内容在每个RenderObject更新时会进行标记的，比如RichText:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">set text(TextSpan value) {
    ···
    switch (_textPainter.text.compareTo(value)) {
      ···
      case RenderComparison.layout:
        _textPainter.text = value;
        _overflowShader = null;
        markNeedsLayout();
        break;
    }
  }
  
void markNeedsLayout() {
    ···
    owner._nodesNeedingLayout.add(this);
    owner.requestVisualUpdate();
  	···
  }
</code></pre></td></tr></table>
</div>
</div><p>查看源码，<code>_layoutWithoutResize</code>方法基本上就是调用<code>performLayout</code>方法，这个方法在每个RenderObject中的实现都不一样，不过约定是进行布局展示后，调用child.layout方法，继续查看这个方法：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void layout(Constraints constraints, { bool parentUsesSize = false }) {
    ···
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
    ···
    if (!_needsLayout &amp;&amp; constraints == _constraints &amp;&amp; relayoutBoundary == _relayoutBoundary) {
      ···
      return;
    }
    _constraints = constraints;
    _relayoutBoundary = relayoutBoundary;
    ···
    if (sizedByParent) {
      ···
      try {
        performResize();
        assert(() { debugAssertDoesMeetConstraints(); return true; }());
      } catch (e, stack) {
        _debugReportException(&#39;performResize&#39;, e, stack);
      }
      ···
    }
    RenderObject debugPreviousActiveLayout;
    ···
    try {
      performLayout();
      markNeedsSemanticsUpdate();
      assert(() { debugAssertDoesMeetConstraints(); return true; }());
    } catch (e, stack) {
      _debugReportException(&#39;performLayout&#39;, e, stack);
    }
    ···
    _needsLayout = false;
    markNeedsPaint();
  }
</code></pre></td></tr></table>
</div>
</div><p>上面这段代码可以分析出很多东西：</p>
<p><code>!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject</code> 这四种判断条件设置relayoutBoundary为RenderObject本身，这有什么用？用处可大了。</p>
<p>约束条件是沿着树的深度，从上到下的，但是layout布局是从下到上的，这个很好理解。大部分情况下，父控件的大小除了约束条件外，还依赖于子控件的大小，所以要从下向上分别layout，当然paint正好相反，是从上到下的顺序，这个后面说明。relayoutBoundary指的是布局边界，也就是说，这个renderObject的大小是固定的，不会因为其子节点的大小而变化，这种情况下就可以认为这个RenderObject就是一个锚点，它的子节点有变动，不会影响到父节点的layout。</p>
<p>那上面这四种情况分别是什么呢？首先还是需要先说明下约束条件</p>
<h3 id="约束条件">约束条件</h3>
<p>为了简单说明，这里只说下<code>BoxConstraints</code>，也就是边界约束，而更为复杂的<code>SliverConstraints</code>其它文章中再详细说明。</p>
<p>先看下BoxConstraints的构造方法，里面就已经包括了所有属性：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">const BoxConstraints({
    this.minWidth = 0.0,
    this.maxWidth = double.infinity,
    this.minHeight = 0.0,
    this.maxHeight = double.infinity
  });
</code></pre></td></tr></table>
</div>
</div><p>这个很好理解，看字面意思就能明白，分别约束了最大/最小宽度、最大/最小高度，也就是要求渲染后的视图一定要在这个范围内展示。而上面的构造方法则展示了最为宽松的约束条件：最小高度或宽度为0，最大高度或宽度为无穷大。</p>
<p>在实际使用时，一般会将父节点的约束条件传递给子节点，如果子节点有额外的约束条件，则进行比对添加，然后再传给下一级。</p>
<p>约束条件很简单，但是根据这四种条件，会有几种类型：</p>
<ul>
<li>tight 如果最小约束(minWidth，minHeight)和最大约束(maxWidth，maxHeight)是一样的，那么就限定死了这个节点的宽度与高度。</li>
<li>loose 如果最小约束都是0.0</li>
<li>bounded 如果最大约束都不是double.infinity</li>
<li>unbounded 如果最大约束都是double.infinity</li>
<li>expanding 如果最小约束和最大约束都是infinite</li>
</ul>
<p>明确约束的概念后，我们继续分析渲染过程。</p>
<h3 id="继续渲染过程分析">继续渲染过程分析</h3>
<p>之前说到了四种情况，下面分别进行说明：</p>
<ul>
<li>!parentUsesSize parentUsesSize表示父节点是否要依赖子节点的size，如果该值为false，子节点要重新布局的时候并不需要通知父节点</li>
<li>sizedByParent sizedByParent表示当前的节点虽然不是isTight，但是通过其他约束属性，也可以明确的知道size，比如Expanded，并不一定需要明确的size</li>
<li>constraints.isTight 这个上面已经说明了</li>
<li>parent is! RenderObject 这个更明确了，父节点都不能进行渲染，自然不能进行size操作</li>
</ul>
<p>非四种情况下，则调用<code>performResize</code>与<code>performLayout</code>遍历所有子节点，直到layout完成。</p>
<p>按照之drawFrame处理，<code>flushLayout</code>完成后进行<code>flushCompositingBits</code>，这个方法是用来为每个RenderObject设置适当needCompositing值，最终needCompositing将会决定生成多少layer提交给引擎，引擎中叠加绘制每一层layer（skia等经典用法，mix也是类似实现）。查看下面代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void flushCompositingBits() {
    profile(() { Timeline.startSync(&#39;Compositing bits&#39;); });
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) =&gt; a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate &amp;&amp; node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    profile(() { Timeline.finishSync(); });
  }

void _updateCompositingBits() {
    if (!_needsCompositingBitsUpdate)
      return;
    final bool oldNeedsCompositing = _needsCompositing;
    _needsCompositing = false;
    visitChildren((RenderObject child) {
      child._updateCompositingBits();
      if (child.needsCompositing)
        _needsCompositing = true;
    });
    if (isRepaintBoundary || alwaysNeedsCompositing)
      _needsCompositing = true;
    if (oldNeedsCompositing != _needsCompositing)
      markNeedsPaint();
    _needsCompositingBitsUpdate = false;
  }
</code></pre></td></tr></table>
</div>
</div><p>每一个layer内容会同步变更，可以将一些paint较为复杂的节点单独设置一个layer，或者经常变动的paint设置为单独layer，这样可以减少多次paint导致的性能耗损。</p>
<p>继续查看<code>flushPaint</code>相关代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void flushPaint() {
    profile(() { Timeline.startSync(&#39;Paint&#39;, arguments: timelineWhitelistArguments); });
    ···
    try {
      final List&lt;RenderObject&gt; dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = &lt;RenderObject&gt;[];
      // Sort the dirty nodes in reverse order (deepest first).
      for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) =&gt; b.depth - a.depth)) {
        assert(node._layer != null);
        if (node._needsPaint &amp;&amp; node.owner == this) {
          if (node._layer.attached) {
            PaintingContext.repaintCompositedChild(node);
          } else {
            node._skippedPaintingOnLayer();
          }
        }
      }
      ···
    } finally {
      ···
      profile(() { Timeline.finishSync(); });
    }
  }
</code></pre></td></tr></table>
</div>
</div><p>基本格式与前面一样，注意<code>PaintingContext.repaintCompositedChild</code>这个方法：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">static void _repaintCompositedChild(
    RenderObject child, {
    bool debugAlsoPaintedParent = false,
    PaintingContext childContext,
  }) {
    ···
    if (child._layer == null) {
      ···
      child._layer = OffsetLayer();
    } else {
      ···
      child._layer.removeAllChildren();
    }
    ···
    childContext ??= PaintingContext(child._layer, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
    childContext.stopRecordingIfNeeded();
  }

void _paintWithContext(PaintingContext context, Offset offset) {
    ···
    if (_needsLayout)
      return;
    ···
    RenderObject debugLastActivePaint;
    ···
    _needsPaint = false;
    try {
      paint(context, offset);
      ···
    }
    ···
  }
</code></pre></td></tr></table>
</div>
</div><p>isRepaintBoundary为true的RenderObject会创建一个自己的layer，最终调用了<code>RenderObject.paint</code>方法。</p>
<p>Flutter会把所有的layer都加入到ui.SceneBuilder对象中。然后在<code>renderView.compositeFrame()</code>中 ui.SceneBuilder会构建出ui.Scene（场景），交给ui.window.render方法去做最后真实渲染，最终绘制过程在Flutter引擎中实现并展示。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">void compositeFrame() {
    Timeline.startSync(&#39;Compositing&#39;, arguments: timelineWhitelistArguments);
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
      ···
    } finally {
      Timeline.finishSync();
    }
  }
</code></pre></td></tr></table>
</div>
</div><p>到现在为止，界面构建过程与渲染过程都已经说明完，那我们跟踪源码的目的是什么呢？至少我们可以做到下面三点：</p>
<ul>
<li>可以对源码进行修改。目前的代码还不是很完善，对于一些特殊的要求，我们可以通过源码修改的方式来完成，而且根据实践，在Android Studio中修改后的源码可以直接参与编译，不需要任何过程。</li>
<li>了解渲染过程，同时也了解了Widget、Element、RenderObject的处理方式以及生命周期，可以自己实现自定义的控件效果。</li>
<li>了解整个渲染过程，出现问题后能够知道瓶颈在哪里，同时增加绘制效率，防止卡顿发生。</li>
</ul>
<h2 id="性能优化">性能优化</h2>
<p>上面主要分析的是ComponentWidget以及相关Element，那就先从这里的优化方向说起。</p>
<p>之前我写过，绘制的锚点在于StatefulWidget，这个Widget的实例是不变的，而State的build方法，会创建出大量新的widget。这些widget对象创建本身就有开销，再加上element的对比判断等等，所以在这方面，我们可以尽可能地提高效率。</p>
<ul>
<li>StatelessWidget本身是不可变的，我们使用的StatefulWidget类都应该先判断是否值得，如果可以的话，应该使用StatelessWidget进行替代。比如每个界面的框架类，如果是不变的，就应该使用StatelessWidget，只有需要变更项才会使用StatefulWidget。</li>
<li>更新的StatefulWidget设计时尽可能地独立开来，非耦合功能拆分展示。</li>
<li>因为每次StatefulWidget的变更都会影响到其下的所有子节点，如果只是有限的几个控件变更，可以将这几个控件单独封装为一个StatefulWidget，单独刷新这个Widget，避免其他控件更新影响效率。这个算是最小封装原则，如下图所示，WidgetA这个控件需要更新，则将其单独封装一个StatefulWidget：</li>
</ul>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214059526.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214059526.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214059526.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214059526.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214059526.png"
        title="image-20210731214059526" /></p>
<ul>
<li>Widget的构造方法以及build方法会经常调用，避免在其中执行太多操作，可以转移的操作放在其它地方执行</li>
<li>可以尝试将部分Widget实例保持不变，比如增加const修饰，或者StreamBuilder方式指定对象等等，但是这样操作需要当心，有可能会引入问题。</li>
<li>如果是自定义控件，采用<code>CustomMultiChildLayout</code>等方式自定义布局展示，可以考虑下使用relayoutBoundary方式，减少节点布局设置</li>
<li>绘制时，一些可能会占用较多资源的控件build操作，可以加到<code>RepaintBoundry</code>控件中，比如静态图片，比较复杂的图片设置为一个单独的layer，避免重复build，在GPU中也会存在缓存，减少开销。</li>
<li>不可见的控件，尽量不进行build操作</li>
<li>自定义控件，避免在绘制时进行创建对象操作，尽可能复用配置，这个和Android是一样的。</li>
<li>尽量减少saveLayer操作，如果是透明效果或者裁剪效果，尽量设置到子控件上。</li>
</ul>
<p>除了与界面渲染相关的优化建议，实际上还有一切其它的性能优化项，比如：</p>
<ul>
<li>部分内容考虑延迟加载</li>
<li>较为耗时的计算操作放置到新的isolate中执行（isolate、Runner与event loop中异步处理的会单独说明，这些还是有很大区别的）</li>
<li>内存加载以及内存泄漏等进行优化</li>
</ul>
<h2 id="flutter生命周期">Flutter生命周期</h2>
<h3 id="state生命周期">State生命周期</h3>
<p>现在使用最多的就是StatefulWidget，先说下State的生命周期。查看State的方法，有这几个需要关注的（按照源码中查找的顺序）：initState、didUpdateWidget、reassemble、setState、deactivate、dispose、build、didChangeDependencies。</p>
<p>其中reassemble是为了开发调试使用的，hot reload时调用该方法，Release版本下该方法不会被调用到，因此通常情况下无需重载该方法。setState与build方法之前已经说明过，不需要再次说明。</p>
<p>其余的几种方法的生命周期如下图：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214048547.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214048547.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214048547.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214048547.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214048547.png"
        title="image-20210731214048547" /></p>
<p>这张图是网上找的，内容比较全面，不过关于deactivate部分还需要再调整下，下面具体来说每一个方法：</p>
<ul>
<li>构造方法不用详细说明，创建State实例后才会执行各种操作。</li>
<li>initState 这个方法只在 <code>void _firstBuild()</code>中调用到，而_firstBuild方法只会在Element的<code>mount</code>方法中调用到，因此<code>initState</code>只会在这个控件第一次创建时才会触发。</li>
<li>didChangeDependencies 这个方法有很多触发地方，首次同样也是在_firstBuild方法中，在initState方法执行后触发。除此以外，还会在<code>notifyDependent</code>方法中触发，而notifyDependent方法在<code>void notifyClients(InheritedWidget oldWidget)</code>方法中调用到，最后的方法是InheritedWidget参数变更时的触发方法（InheritedWidget的具体原理以及常用方式在以后会详细说明）。
所以总结一下，didChangeDependencies有两种执行时机：1、会在initState之后执行；2、会在依赖的InheritedWidget发生变化的时执行</li>
<li>didUpdateWidget 这个方法会在<code>StatefulElement.update(StatefulWidget newWidget)</code>中执行，而后面一个方法我们很熟悉了，就是之前判断Widget变更的四种条件之一了，再看下源码：</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">if (Widget.canUpdate(child.widget, newWidget)) {
	if (child.slot != newSlot)
	  updateSlotForChild(child, newSlot);
	child.update(newWidget);
	return child;
}
</code></pre></td></tr></table>
</div>
</div><p>所以didUpdateWidget的执行时机在于Widget实例变更了，但是Element实例没有变更的情况，也就是runtimeType与Key一致的情况。</p>
<ul>
<li>deactivate 调用时机同样在widget变更的四种条件之一，节点树构建时，发现某个节点不存在了，将其设置状态为deactvate。实际上deactivate调用后并不一定会直接调用dispose方法，framework在某些情况下会将remove掉的子树重新设置到其他位置，这时候会调用deactivate以及build方法，但不会调用dispose方法。例如路由设置，A界面跳转到B界面，这时A界面就会触发deactivate以及build方法。同样的如果从B界面返回到A界面，framework需要重新将子树放回放回原来的位置，同样会触发A界面的deactivate以及build方法。</li>
<li>dispose 这个方法就很明确了，当Element销毁时调用该方法，调用时机在<code>unmount</code>中。</li>
</ul>
<h3 id="app生命周期">App生命周期</h3>
<p>App生命周期监听可以通过<code>WidgetsBindingObserver</code>类进行设置，里面存在一个<code>void didChangeAppLifecycleState(AppLifecycleState state)</code>方法，实际使用时可以通过mixin方式进行监听。例如：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-fallback" data-lang="fallback">class _MyHomePageState extends State&lt;MyHomePage&gt; with WidgetsBindingObserver {
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch(state) {
      case AppLifecycleState.inactive:
        print(&#39;inactive state.&#39;);
        break;
      case AppLifecycleState.resumed:
        print(&#39;resumed state.&#39;);
        break;
      case AppLifecycleState.paused:
        print(&#39;paused state.&#39;);
        break;
      case AppLifecycleState.suspending:
        print(&#39;suspending state.&#39;);
        break;
      default:
        print(&#39;unknown state.&#39;);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
</code></pre></td></tr></table>
</div>
</div><p>这四种状态分别进行说明：</p>
<ul>
<li>resumed 与Android类似，表示界面可见，可响应事件</li>
<li>inactive 表示无法获取焦点，无法响应用户事件，但是会有drawFrame回调，比如弹出dialog情况</li>
<li>paused 应用挂起，这种情况下drawFrame回调也不会有，比如退到后台</li>
<li>suspending ios中没有该状态，pause之后的状态，应用停止。该状态不常用</li>
</ul>
<p>常见的状态切换：</p>
<p>应用退到后台：inactive -&gt; paused</p>
<p>应用后台转到前台： inactive -&gt; resumed</p>
<h3 id="widgetelement-与-renderobject生命周期">Widget、Element 与 RenderObject生命周期</h3>
<p>之前的源码分析中已经做了详细的说明，有空的时候画一张图补上吧，下面是找的一张网上图片：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214012378.png"
        data-srcset="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214012378.png, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214012378.png 1.5x, https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214012378.png 2x"
        data-sizes="auto"
        alt="https://luckly007.oss-cn-beijing.aliyuncs.com/img/image-20210731214012378.png"
        title="image-20210731214012378" /></p></div><div class="post-footer" id="post-footer">
    <div class="post-info">
        <div class="post-info-line">
            <div class="post-info-mod">
                <span>更新于 2021-07-31</span>
            </div>
            <div class="post-info-license"></div>
        </div>
        <div class="post-info-line">
            <div class="post-info-md"></div>
            <div class="post-info-share">
                <span><a href="javascript:void(0);" title="分享到 Twitter" data-sharer="twitter" data-url="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" data-title="《Flutter渲染原理与性能优化》" data-hashtags="flutter"><i class="fab fa-twitter fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Facebook" data-sharer="facebook" data-url="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" data-hashtag="flutter"><i class="fab fa-facebook-square fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Hacker News" data-sharer="hackernews" data-url="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" data-title="《Flutter渲染原理与性能优化》"><i class="fab fa-hacker-news fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Line" data-sharer="line" data-url="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" data-title="《Flutter渲染原理与性能优化》"><i data-svg-src="https://cdn.jsdelivr.net/npm/simple-icons@2.14.0/icons/line.svg"></i></a><a href="javascript:void(0);" title="分享到 微博" data-sharer="weibo" data-url="https://luckly.work/flutter%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" data-title="《Flutter渲染原理与性能优化》" data-image="https://luckly007.oss-cn-beijing.aliyuncs.com/img/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"><i class="fab fa-weibo fa-fw"></i></a></span>
            </div>
        </div>
    </div>

    <div class="post-info-more">
        <section class="post-tags"><i class="fas fa-tags fa-fw"></i>&nbsp;<a href="/tags/flutter/">flutter</a></section>
        <section>
            <span><a href="javascript:void(0);" onclick="window.history.back();">返回</a></span>&nbsp;|&nbsp;<span><a href="/">主页</a></span>
        </section>
    </div>

    <div class="post-nav"><a href="/flutter%E6%B5%8B%E8%AF%95%E8%84%9A%E6%9C%AC%E5%BC%80%E5%8F%91/" class="prev" rel="prev" title="《Flutter测试脚本开发》"><i class="fas fa-angle-left fa-fw"></i>《Flutter测试脚本开发》</a>
            <a href="/flutter%E7%95%8C%E9%9D%A2%E8%B7%B3%E8%BD%AC%E6%95%B0%E6%8D%AE%E4%BA%A4%E4%BA%92%E7%95%8C%E9%9D%A2%E6%B7%B7%E5%90%88%E5%BC%80%E5%8F%91/" class="next" rel="next" title="《Flutter界面跳转数据交互界面混合开发》">《Flutter界面跳转数据交互界面混合开发》<i class="fas fa-angle-right fa-fw"></i></a></div>
</div>
</article></div>
            </main><footer class="footer">
        <div class="footer-container"><div class="footer-line">由 <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer" title="Hugo 0.79.1">Hugo</a> 强力驱动 | 主题 - <a href="https://github.com/dillonzq/LoveIt" target="_blank" rel="noopener noreffer" title="LoveIt 0.2.10"><i class="far fa-kiss-wink-heart fa-fw"></i> LoveIt</a>
                </div><div class="footer-line"><i class="far fa-copyright fa-fw"></i><span itemprop="copyrightYear">2021</span><span class="author" itemprop="copyrightHolder">&nbsp;<a href="/" target="_blank">早起的年轻人</a></span>&nbsp;|&nbsp;<span class="license"><a rel="license external nofollow noopener noreffer" href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">CC BY-NC 4.0</a></span></div>
        </div>
    </footer></div>

        <div id="fixed-buttons"><a href="#" id="back-to-top" class="fixed-button" title="回到顶部">
                <i class="fas fa-arrow-up fa-fw"></i>
            </a><a href="#" id="view-comments" class="fixed-button" title="查看评论">
                <i class="fas fa-comment fa-fw"></i>
            </a>
        </div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.1/build/cookieconsent.min.css"><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/smooth-scroll@16.1.3/dist/smooth-scroll.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/autocomplete.js@0.37.1/dist/autocomplete.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/algoliasearch@4.2.0/dist/algoliasearch-lite.umd.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lazysizes@5.2.2/lazysizes.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/sharer.js@0.4.0/sharer.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/cookieconsent@3.1.1/build/cookieconsent.min.js"></script><script type="text/javascript">window.config={"code":{"copyTitle":"复制到剪贴板","maxShownLines":10},"comment":{},"cookieconsent":{"content":{"dismiss":"同意","link":"了解更多","message":"本网站使用 Cookies 来改善您的浏览体验."},"enable":true,"palette":{"button":{"background":"#f0f0f0"},"popup":{"background":"#1aa3ff"}},"theme":"edgeless"},"search":{"algoliaAppID":"PASDMWALPK","algoliaIndex":"index.zh-cn","algoliaSearchKey":"b42948e51daaa93df92381c8e2ac0f93","highlightTag":"em","maxResultLength":10,"noResultsFound":"没有找到结果","snippetLength":50,"type":"algolia"}};</script><script type="text/javascript" src="/js/theme.min.js"></script></body>
</html>
